/*
 * GTFSExporter.java
 * 
 * Created by demory on Feb 20, 2010, 5:44:09 PM
 * 
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fpdev.core.transit.gtfs;

import java.awt.geom.Point2D;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
import org.fpdev.core.FPEngine;
import org.fpdev.core.Scenario;
import org.fpdev.core.basenet.BLink;
import org.fpdev.core.basenet.BNode;
import org.fpdev.core.basenet.Path;
import org.fpdev.core.transit.CalendarService;
import org.fpdev.core.transit.Provider;
import org.fpdev.core.transit.Route;
import org.fpdev.core.transit.Station;
import org.fpdev.core.transit.Stop;
import org.fpdev.core.transit.SubRoute;
import org.fpdev.core.transit.Timetable;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.referencing.CRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;

/**
 *
 * @author demory
 */
public class GTFSExporter {


  private FPEngine engine_;

  private MathTransform mt_;

  private Provider prov_;
  private String dir_;

  public GTFSExporter(FPEngine engine, String dir) {
    engine_ = engine;
    dir_ = dir;

    try {
      //System.out.println("sourceCRS: "+engine_.getDataPackage().getProperty("crsCode"));
        CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:2240");//engine_.getDataPackage().getProperty("crsCode"));
        CoordinateReferenceSystem destCRS = CRS.decode("EPSG:4326");//4269");
        mt_ = CRS.findMathTransform(sourceCRS, destCRS);
    } catch (Exception ex) {
      throw new IllegalStateException("error creating MathTransform for GTFSExporter", ex);
    }

  }

  public void writeGTFS(Provider prov, Scenario scen) {
    prov_ = prov;
    writeAgencyFile(scen);
    writeCalendarFile(scen);
    writeRoutesFile(scen);
    writeStopsFile(scen);
    writeTripsFile(scen);
    writeStopTimesFile(scen);
    writeShapesFile(scen);
  }

  private void writeAgencyFile(Scenario scen) {
    try {
      FileWriter writer = new FileWriter(dir_ + "agency.txt");
      writer.write("agency_name,agency_url,agency_timezone\n");
      writer.write(prov_.getName()+",http://www.itsmarta.com,America/New_York\n");
      writer.close();
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }

  private void writeCalendarFile(Scenario scen) {
    try {
      FileWriter writer = new FileWriter(dir_ + "calendar.txt");
      writer.write("service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date\n");
      for(CalendarService cs : engine_.getRoutes().getCalendarServices().getServices()) {
        String line = cs.getID()+",";
        line += (cs.containsDay(Calendar.MONDAY) ? "1" : "0") +",";
        line += (cs.containsDay(Calendar.TUESDAY) ? "1" : "0") +",";
        line += (cs.containsDay(Calendar.WEDNESDAY) ? "1" : "0") +",";
        line += (cs.containsDay(Calendar.THURSDAY) ? "1" : "0") +",";
        line += (cs.containsDay(Calendar.FRIDAY) ? "1" : "0") +",";
        line += (cs.containsDay(Calendar.SATURDAY) ? "1" : "0") +",";
        line += (cs.containsDay(Calendar.SUNDAY) ? "1" : "0") +",";
        line += "20100101,20101231\n";
        writer.write(line);
      }
      writer.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void writeRoutesFile(Scenario scen) {
    try {
      FileWriter writer = new FileWriter(dir_ + "routes.txt");
      writer.write("route_id,route_short_name,route_long_name,route_type\n");
      for(Route rte : engine_.getRoutes().getCollectionSortedByID(scen, true, prov_)) {
        if(rte.subRouteCount() == 0) continue;
        writer.write(rte.getCombinedID()+","+(rte.getShowID() ? rte.getID() : "")+","+rte.getName()+","+getGTFSRouteType(rte)+"\n");
      }
      writer.close();
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
  
  public static int getGTFSRouteType(Route rte) {
    switch(rte.getRepresentativeType()) {
      case BUS: return 3;
      case METRO: return 1;
      case LIGHTRAIL: return 0;
    }
    return -1;
  }

  private void writeStopsFile(Scenario scen) {
    try {
      HashSet visitedStopIDs = new HashSet<Integer>();

      FileWriter writer = new FileWriter(dir_ + "stops.txt");
      writer.write("stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n");

      for(Station station : engine_.getStations().getCollection()) {
        /*boolean scenCheck = false;
        for(SubRoute sub : station.getServices(engine_.getBaseNet())) {
          if(sub.getRoute().getScenario().isAccessibleTo(scen)) {
            scenCheck = true;
            break;
          }
        }
        if(!scenCheck) continue;*/
        Point2D pt = station.getCoordinate();
        DirectPosition2D srcDP = new DirectPosition2D(pt.getX(), pt.getY());
        DirectPosition2D destDP = new DirectPosition2D();
        mt_.transform(srcDP, destDP);
        writer.write(station.getName()+","+station.getName()+","+destDP.x+","+destDP.y+",1,\n");
      }
      for(Route rte : engine_.getRoutes().getCollectionSortedByID(scen, true, prov_)) {
        if(rte.subRouteCount() == 0) continue;
        for(SubRoute sub : rte.getSubRoutes()) {
          for(Stop stop : sub.getPath().getStops()) {
            if(visitedStopIDs.contains(stop.getNodeID())) continue;
            String name = stop.getNode().getDisplayName(engine_.getBaseNet());
            if(name.startsWith("(")) name = "Unknown Location";
            DirectPosition2D srcDP = new DirectPosition2D(stop.getNode().getX(), stop.getNode().getY());
            DirectPosition2D destDP = new DirectPosition2D();
            mt_.transform(srcDP, destDP);
            String station = stop.getNode().isStation() ? stop.getNode().getStation().getName() : "";
            writer.write(stop.getNodeID()+","+name+","+destDP.x+","+destDP.y+",0,"+station+"\n");
            visitedStopIDs.add(stop.getNodeID());
          }
        }
      }
      writer.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void writeTripsFile(Scenario scen) {
    try {
      FileWriter writer = new FileWriter(dir_ + "trips.txt");
      writer.write("route_id,service_id,trip_id,shape_id\n");
      for(Route rte : engine_.getRoutes().getCollectionSortedByID(scen, true, prov_)) {
        if(rte.subRouteCount() == 0) continue;
        for(SubRoute sub : rte.getSubRoutes()) {
          for(Timetable table : sub.getTables()) {
            for(int r = 0; r < table.runCount(); r++) {
              String routeID = sub.getRoute().getCombinedID();
              String serviceID = table.getService().getID();
              String tripID = sub.getMasterID()+"."+serviceID+(r+1);
              String shapeID = sub.getMasterID();
              writer.write(routeID+","+serviceID+","+tripID+","+shapeID+"\n");
            }
          }
        }
      }
      writer.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void writeStopTimesFile(Scenario scen) {
    try {
      FileWriter writer = new FileWriter(dir_ + "stop_times.txt");
      writer.write("trip_id,arrival_time,departure_time,stop_id,stop_sequence\n");
      for(Route rte : engine_.getRoutes().getCollectionSortedByID(scen, true, prov_)) {
        if(rte.subRouteCount() == 0) continue;
        for(SubRoute sub : rte.getSubRoutes()) {
          for(Timetable table : sub.getTables()) {
            for(int r = 0; r < table.runCount(); r++) {
              String tripID = sub.getMasterID()+"."+table.getService().getID()+(r+1);
              int stopSeq = 0;
              for(Stop stop : sub.getPath().getStopList()) {
                String time = stop.isTimePoint() ? ""+toGTFSTime(table.getTime(r, stopSeq)) : "";
                int stopID = stop.getNodeID();
                writer.write(tripID+","+time+","+time+","+stopID+","+(stopSeq+1)+"\n");
                stopSeq++;
              }
            }
          }
        }
      }
      writer.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private String toGTFSTime(int stime) {

    int hr = (stime/3600) + 4;
    int min = (stime/60) % 60;
    int sec = stime % 60;

    return (hr < 10 ? "0"+hr : hr) + ":" + (min < 10 ? "0"+min : min) + ":" + (sec < 10 ? "0"+sec : sec);

  }

  private void writeShapesFile(Scenario scen) {
    try {
      FileWriter writer = new FileWriter(dir_ + "shapes.txt");
      writer.write("shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence\n");
      for(Route rte : engine_.getRoutes().getCollectionSortedByID(scen, true, prov_)) {
        if(rte.subRouteCount() == 0) continue;
        for(SubRoute sub : rte.getSubRoutes()) {
          String shapeID = sub.getMasterID();
          int ptSeq = 1;
          Path path = sub.getPath();

          Iterator<BNode> nodes = path.getNodes();
          Iterator<BLink> links = path.getLinks();
          while (nodes.hasNext()) {
            BNode node = nodes.next();

            DirectPosition2D srcDP = new DirectPosition2D(node.getX(), node.getY());
            DirectPosition2D destDP = new DirectPosition2D();
            mt_.transform(srcDP, destDP);

            writer.write(shapeID+","+destDP.x+","+destDP.y+","+ptSeq+"\n");
            ptSeq++;

            if (links.hasNext()) { // shapepoints
              BLink link = links.next();
              ListIterator<Point2D.Double> shpPts = link.getShapePointsList().listIterator();
              boolean reverse = (node == link.getTNode());
              while (reverse ? shpPts.hasPrevious() : shpPts.hasNext()) {
                Point2D.Double pt = (reverse ? shpPts.previous() : shpPts.next());
                srcDP = new DirectPosition2D(pt.x, pt.y);
                mt_.transform(srcDP, destDP);

                writer.write(shapeID+","+destDP.x+","+destDP.y+","+ptSeq+"\n");
                ptSeq++;
              }
            }
          }

        }
      }
      writer.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
